#pragma once
#include <stdexcept>
#include <algorithm>
#include <typeinfo>
#include <vector>
#include <map>
#include <any>
#include <functional>
#include <iostream>
#include <climits>
#include <fstream>
#include <thread>
#include <future>
#include <chrono>
#include <iomanip>
#include <sstream>
#include "CMacro.h"

/** @note All PlatUtil class must be exactly pure common util, must not include functional class !*/
class CUtil {
public:
	static void readFileAsString(std::string& out, const std::string& filePath);

	static void readFileAsBytes(CByteArray& out, const std::string& filePath);

	static bool saveMsgToFile(const std::string& msg, const std::string& path);

	static UChar* toCStyleCharArray(CByteArray& charArray) { charArray.push_back('\0'); return charArray.data(); }

	static void includeFile(std::string& outResult, const std::string& filePath, const std::string& includeFilePath, char* inject = NULL);

	template<typename Func, typename... Args>
	static void doInBgThread(Func&& func, Args&&... args) {
		std::thread([=]() mutable {
			func(std::forward<Args>(args)...);
		}).detach();
	}

	/**
	 * @warning use with caution! if you want return value, you can use it, but! if you do not need return value, do not use it!
	 *          because the return value's lifecycle determine whether the task block the caller's thead!
	 */
	static void launchAsync(const std::function<void()>& func) { static std::future<void> ret = std::async(std::launch::async, func); }

	static auto currentThreadID() { return std::this_thread::get_id(); }// can std::cout directly

	static void msleep(int milliseconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); }

	/** delay in new thread, not caller's thread! */
	static void delay(const std::function<void()>& func, int delayMs);

	static void loop(const std::function<void()>& func, int intervalMs);

	static long long currentTimeMillis();

	static std::string currentTime();

	static bool fileExists(const std::string& path);

	static bool mkdirIfNotExists(const std::string& path);

	static std::string getParentPath(const std::string& fullPath);

	static bool copy(const std::string& fromPath, const std::string& toPath);

	static bool filesOfDir(std::vector<std::string>& outFileNames, const std::string& dirPath, const std::string& extension, bool asc = true);

	inline static float mpsToKmh(float speedMps) { return speedMps * 3.6; }

	static void printCompilerInfo();
	static void checkGLMultisamplingStatus();

	template <typename T>
	static const char* getObjectClassName(T t) { return typeid(t).name(); }

	static bool isApproxEqual(float a, float b, float epsilon = 1e-6) { return std::abs(a - b) < epsilon; }
	static void saveMapToLocalJson(const std::map<std::string, std::any>& KVs, const std::string& localPath);
	static void getMapFromLocalJson(std::map<std::string, std::any>& outKVs, const std::string& localPath);
private:
	CUtil() = default;
};